package com.boss.core.user;

import com.boss.core.BossConstants;
import com.boss.core.manage.BossEnvironment;
import com.boss.core.manage.BossPort;
import com.boss.core.manage.BossServer;
import com.boss.core.util.BossGlobalUtil;
import com.boss.core.util.BossUtil;
import com.boss.Messages;
import hudson.Extension;
import hudson.Functions;
import hudson.RelativePath;
import hudson.model.Hudson;
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
import hudson.security.AuthorizationStrategy;
import hudson.security.GlobalMatrixAuthorizationStrategy;
import hudson.util.ListBoxModel;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Boss Extended User Profile
 * @author Momin Noor Khan
 */
public class BossUserProfile {
    public static class BossUserProperty extends UserProperty {
        private List<BossEnvironment> userEnvironments;

        //Getters and Setters
        public BossUserProperty(List<BossEnvironment> userEnvironments) {
            this.userEnvironments = userEnvironments;
        }

        public List<BossEnvironment> getUserEnvironments() {
            return userEnvironments;
        }

        /**
         * Get list of enabled/disabled environments on which this user is authorized
         * @param isDisabled
         * @return
         */
        public List<BossEnvironment> getUserEnvironments(boolean isDisabled) {
            List<BossEnvironment> resultList = new ArrayList<BossEnvironment>();

            if (userEnvironments != null) {
                for (BossEnvironment e : userEnvironments) {
                    if (e.isDisabled() == isDisabled) {
                        resultList.add(e);
                    }
                }
            }

            return resultList;
        }

        /**
         * Get the specified environment for this user
         * @param environmentName
         * @return
         */
        public BossEnvironment getUserEnvironment(String environmentName) {
            if (userEnvironments != null) {
                for (BossEnvironment e : userEnvironments) {
                    if (e.getEnvironmentName().equalsIgnoreCase(environmentName)) {
                        return e;
                    }
                }
            }
            return null;
        }

        /**
         * Get the servers for the specified environment for this user
         * @param environmentName
         * @return
         */
        public List<BossServer> getUserServers(String environmentName) {
            BossEnvironment environment = getUserEnvironment(environmentName);
            if (environment != null) {
                return environment.getServers();
            }
            return null;
        }

        /**
         * Get the enabled/disabled servers for the specified environment for this user
         * @param environmentName
         * @param isDisabled
         * @return
         */
        public List<BossServer> getUserServers(String environmentName, boolean isDisabled) {
            List<BossServer> resultList = new ArrayList<BossServer>();
            BossEnvironment environment = getUserEnvironment(environmentName);
            if (environment != null) {
                for (BossServer s : environment.getServers()) {
                    if (s.isDisabled() == isDisabled) {
                        resultList.add(s);
                    }
                }
            }
            return resultList;
        }

        /**
         * Get the specified server for the given environment for this user
         * @param environmentName
         * @param serverName
         * @return
         */
        public BossServer getUserServer(String environmentName, String serverName) {
            BossEnvironment environment = getUserEnvironment(environmentName);
            if (environment != null) {
                List<BossServer> servers = environment.getServers();
                for (BossServer s : servers) {
                    if (s.getServerName().equalsIgnoreCase(serverName)) {
                        return s;
                    }
                }
            }
            return null;
        }

        /**
         * Get the list of ports for the specified server in the given environment for this user
         * @param environmentName
         * @param serverName
         * @return
         */
        public List<BossPort> getUserPorts(String environmentName, String serverName) {
            List<BossPort> ports = new ArrayList<BossPort>();
            BossServer server = getUserServer(environmentName, serverName);
            if (server != null) {
                ports = server.getPorts();
            }
            return ports;
        }

        /**
         * Get the enabled/disabled ports for the specified server in the given environment for this user
         * @param environmentName
         * @param serverName
         * @param isEnabled
         * @return
         */
        public List<BossPort> getUserPorts(String environmentName, String serverName, boolean isEnabled) {
            List<BossPort> resultList = new ArrayList<BossPort>();

            BossServer server = getUserServer(environmentName, serverName);
            if (server != null) {
                for (BossPort p : server.getPorts()) {
                    if (p.isEnabled() == isEnabled) {
                        resultList.add(p);
                    }
                }
            }
            return resultList;
        }

        /**
         * Get the specified port for the given server and environments for this user
         * @param environmentName
         * @param serverName
         * @param portName
         * @return
         */
        public BossPort getUserPort(String environmentName, String serverName, String portName) {
            BossServer server = getUserServer(environmentName, serverName);
            if (server != null) {
                for (BossPort p : server.getPorts()) {
                    if (p.getPortName().equalsIgnoreCase(portName)) {
                        return p;
                    }
                }
            }
            return null;
        }

        /**
         * Get available environments for this user
         * @return
         */
        public List<BossEnvironment> getAvailableEnvironments() {
            List<BossEnvironment> resultList = new ArrayList<BossEnvironment>();
            List<BossEnvironment> enabledEnvironments = BossGlobalUtil.getEnabledEnvironments();
            List<BossEnvironment> enabledEnvironments4User = getUserEnvironments(false);

            if (isUserAuthorizedForAllContainers()) {
                return enabledEnvironments;
            }

            for (BossEnvironment environment : enabledEnvironments4User) {
                if (enabledEnvironments.contains(environment)) {
                    resultList.add(environment);
                }
            }
            return resultList;
        }

        /**
         * Get available servers for the specified environment for this user
         * @param environment
         * @return
         */
        public List<BossServer> getAvailableServers(BossEnvironment environment) {
            List<BossServer> resultList = new ArrayList<BossServer>();
            String envName;
            if (environment != null) {
                envName = environment.getEnvironmentName();
                List<BossServer> enabledServers = BossGlobalUtil.getEnabledServers(envName);
                List<BossServer> enabledServers4User = getUserServers(envName, false);

                if (isUserAuthorizedForAllContainers()) {
                    return enabledServers;
                }

                for (BossServer server : enabledServers4User) {
                    if (enabledServers.contains(server)) {
                        resultList.add(server);
                    }
                }
            }
            return resultList;
        }

        /**
         * Get available ports for the specified environment and server for this user
         * @param environment
         * @param server
         * @return
         */
        public List<BossPort> getAvailablePorts(BossEnvironment environment, BossServer server) {
            List<BossPort> resultList = new ArrayList<BossPort>();
            String envName;
            String svrName;
            if (environment != null && server != null) {
                envName = environment.getEnvironmentName();
                svrName = server.getServerName();
                List<BossPort> enabledPorts = BossGlobalUtil.getEnabledPorts(envName, svrName);
                List<BossPort> enabledPorts4User = getUserPorts(envName, svrName, true);

                if (isUserAuthorizedForAllContainers()) {
                    return enabledPorts;
                }

                for (BossPort port : enabledPorts4User) {
                    if (enabledPorts.contains(port)) {
                        resultList.add(port);
                    }
                }
            }
            return resultList;
        }


        /**
         * just for testing data population- remove later
         * finally we have to use userEnvironments to populate repeatable section
         *
         * @return
         */
        public List<BossEnvironment> getEnabledEnvironments() {
            return BossGlobalUtil.getEnabledEnvironments();
        }

        /**
         * Get enabled servers from the boss config for the specified environment name
         * @param environmentName
         * @return
         */
        public List<BossServer> getEnabledServers(String environmentName) {
            return BossGlobalUtil.getEnabledServers(environmentName);
        }

        /**
         * Check if this user has administer permission in hudson
         * @param userName
         * @return
         */
        public boolean isGlobalAdmin(String userName) {
            AuthorizationStrategy authStrategy = Hudson.getInstance().getAuthorizationStrategy();

            //We are using matrix-based authorization strategy only
            //a generic approach would be to handle other strategies gracefully too instead of returning false for them
            //but for the time being the following would suffice
            if (authStrategy instanceof GlobalMatrixAuthorizationStrategy) {
                //String sid = user.getId();

                if (((GlobalMatrixAuthorizationStrategy) authStrategy).hasPermission(userName, Hudson.ADMINISTER)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Check if this user has admin role in boss
         * @param userName
         * @return
         */
        public boolean isBossAdmin(String userName) {
            boolean result = BossUtil.isUserInBossRole(userName, BossConstants.ROLE_ADMIN);
            return result;
        }

        /**
         * Check if this user has super user role in boss
         * @param userName
         * @return
         */
        public boolean isBossSuperUser(String userName) {
            boolean result = BossUtil.isUserInBossRole(userName, BossConstants.ROLE_SUPER_USER);
            return result;
        }

        /**
         * Check if this user is authorized on all containers
         * @return
         */
        public boolean isUserAuthorizedForAllContainers() {
            String userName = user.getId();

            if (isGlobalAdmin(userName)) {
                return true;
            } else if (isBossAdmin(userName)) {
                return true;
            } else if (isBossSuperUser(userName)) {
                return true;
            }
            return false;
        }

        /**
         * Get authorization message to display
         * @return
         */
        public String getAuthorizationMessage() {
            return Messages.BossUserProfile_AuthorizedOnAllContainers();
        }

        /**
         * Get message to display for available contianers
         * @return
         */
        public String getAvailableContainersMessage() {
            return Messages.BossUserProfile_AvailableContainers();
        }

        @Extension
        public static final class DescriptorImpl extends UserPropertyDescriptor {
            /**
             * Get display name for the extended user profile section, visible only to admins
             * @return
             */
            public String getDisplayName() {
                try {
                    if (Functions.hasPermission(Hudson.ADMINISTER)) {
                        return "BOSS - Extended User Profile";
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ServletException e) {
                    e.printStackTrace();
                }
                return null;
            }

            public BossUserProperty newInstance(User user) {
                return new BossUserProperty(null);
            }

            @Override
            public BossUserProperty newInstance(StaplerRequest req, JSONObject formData) throws FormException {
                List<BossEnvironment> userEnvironments = new ArrayList<BossEnvironment>();
                List<BossEnvironment> tmpEnvironments = new ArrayList<BossEnvironment>();

                //remove duplicates
                tmpEnvironments.addAll(req.bindJSONToList(BossEnvironment.class, formData.get("environments")));
                userEnvironments = BossUtil.getUniqueEnvironments(tmpEnvironments);

                if (!userEnvironments.isEmpty()) {
                    Collections.sort(userEnvironments);
                }
                return new BossUserProperty(userEnvironments);
            }

            /**
             * Populate environment drop down on the extended user profile
             * @return
             */
            public ListBoxModel doFillEnvironmentNameItems() {
                return getListBoxModel4Environment();
            }

            /**
             * Populate server drop down on the extended user profile
             * @param environmentName
             * @return
             */
            public ListBoxModel doFillServerNameItems(@QueryParameter("environmentName") @RelativePath(value = "..") String environmentName) {
                return getListBoxModel4Server(environmentName);
            }

            /**
             * Populate port drop down on the extended user profile
             * @param environmentName
             * @param serverName
             * @return
             */
            public ListBoxModel doFillPortNameItems(@QueryParameter("environmentName") @RelativePath(value = "../..") String environmentName,
                                                    @QueryParameter("serverName") @RelativePath(value = "..") String serverName) {
                return getListBoxModel4Port(environmentName, serverName);
            }

            /**
             * Get list box model for environment
             * @return
             */
            public ListBoxModel getListBoxModel4Environment() {
                return getListBoxModel4Environment(true);
            }

            /**
             * Get list box model for server
             * @param envName
             * @return
             */
            public ListBoxModel getListBoxModel4Server(String envName) {
                return getListBoxModel4Server(envName, true);
            }

            /**
             * Get list box model for port
             * @param envName
             * @param svrName
             * @return
             */
            public ListBoxModel getListBoxModel4Port(String envName, String svrName) {
                return getListBoxModel4Port(envName, svrName, true);
            }

            /**
             * Get list box model for environment
             * @param isDummyValueRequired
             * @return
             */
            public ListBoxModel getListBoxModel4Environment(boolean isDummyValueRequired) {
                ListBoxModel model;
                List<String> list;

                list = getAvailableEnvironmentValues();
                if (isDummyValueRequired) {
                    list.add(0, BossConstants.DUMMY_VALUE);
                }

                model = convert2ListBoxModel(list);
                return model;
            }

            /**
             * Get list box model for server
             * @param key
             * @param isDummyValueRequired
             * @return
             */
            public ListBoxModel getListBoxModel4Server(String key, boolean isDummyValueRequired) {
                ListBoxModel model;
                List<String> list = new ArrayList<String>();

                if (!StringUtils.isEmpty(key) && !key.equals(BossConstants.DUMMY_VALUE)) {
                    list = getAvailableServerValues(key);
                    if (isDummyValueRequired) {
                        list.add(0, BossConstants.DUMMY_VALUE);
                    }
                }
                model = convert2ListBoxModel(list);
                return model;
            }

            /**
             * Get the list box model for port
             * @param envName
             * @param svrName
             * @param isDummyValueRequired
             * @return
             */
            public ListBoxModel getListBoxModel4Port(String envName, String svrName, boolean isDummyValueRequired) {
                ListBoxModel model;
                List<String> list = new ArrayList<String>();

                if ((!StringUtils.isEmpty(envName) && !envName.equals(BossConstants.DUMMY_VALUE))
                        &&
                        (!StringUtils.isEmpty(svrName) && !svrName.equals(BossConstants.DUMMY_VALUE))) {
                    list = getAvailablePortValues(envName, svrName);
                    if (isDummyValueRequired) {
                        list.add(0, BossConstants.DUMMY_VALUE);
                    }
                }
                model = convert2ListBoxModel(list);
                return model;
            }

            /**
             * Convert string list to listbox model
             * @param tmpList
             * @return
             */
            public ListBoxModel convert2ListBoxModel(List<String> tmpList) {
                ListBoxModel model = new ListBoxModel();
                List<String> list = new ArrayList<String>();

                if (tmpList != null) {
                    for (String s : tmpList) {
                        list.add(s);
                    }
                }

                for (String s : list) {
                    model.add(String.format("%s", s), s);
                }
                return model;
            }

            /**
             * Get available environment values
             * @return
             */
            public List<String> getAvailableEnvironmentValues() {
                List<String> resultList = new ArrayList<String>();
                List<BossEnvironment> eSections = BossGlobalUtil.getEnvironments();

                //List<BossEnvironment> tmpList = new ArrayList<BossEnvironment>();
                newInstance(Hudson.getInstance().getMe());

                for (BossEnvironment e : eSections) {
                    if (!StringUtils.isBlank(e.getEnvironmentName())) {
                        resultList.add(e.getEnvironmentName());
                    }
                }
                return resultList;
            }

            /**
             * Get available server values
             * @param key
             * @return
             */
            public List<String> getAvailableServerValues(String key) {
                List<String> resultList = new ArrayList<String>();
                List<BossServer> serverSections = BossGlobalUtil.getServers(key);

                //List<BossServer> tmpList = new ArrayList<BossServer>();
                for (BossServer svr : serverSections) {
                    if (!StringUtils.isBlank(svr.getServerName())) {
                        resultList.add(svr.getServerName());
                    }
                }
                return resultList;
            }

            /**
             * Get available port values
             * @param environmentName
             * @param serverName
             * @return
             */
            public List<String> getAvailablePortValues(String environmentName, String serverName) {
                List<String> resultList = new ArrayList<String>();
                List<BossPort> portSections = BossGlobalUtil.getPorts(environmentName, serverName);

                for (BossPort prt : portSections) {
                    if (!StringUtils.isBlank(prt.getPortName())) {
                        resultList.add(prt.getPortName());
                    }
                }
                return resultList;
            }
        }
    }
}
